package com.iwhalecloud.citybrain.flow.platform.manager.infrastructure.repository;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import com.iwhalecloud.citybrain.flow.platform.manager.domain.model.calendar.OffDayAddVo;
import com.iwhalecloud.citybrain.flow.platform.manager.domain.model.calendar.OffDayInfoVo;
import com.iwhalecloud.citybrain.flow.platform.manager.domain.model.calendar.WorkingDayAndOffDayAddVo;
import com.iwhalecloud.citybrain.flow.platform.manager.domain.model.calendar.WorkingDayInfoVo;
import com.iwhalecloud.citybrain.flow.platform.manager.domain.model.calendar.WorkingDayQueryVo;
import com.iwhalecloud.citybrain.flow.platform.manager.domain.repository.WorkingDayRepository;
import com.iwhalecloud.citybrain.flow.platform.manager.application.common.enums.WeekEnum;
import com.iwhalecloud.citybrain.flow.platform.manager.application.common.enums.WorkingDayTypeEnum;
import com.iwhalecloud.citybrain.flow.platform.manager.infrastructure.entities.FlowWorkingDay;
import com.iwhalecloud.citybrain.flow.platform.manager.infrastructure.entities.FlowWorkingDayExample;
import com.iwhalecloud.citybrain.flow.platform.manager.infrastructure.entities.FlowWorkingDayWeek;
import com.iwhalecloud.citybrain.flow.platform.manager.infrastructure.entities.FlowWorkingDayWeekExample;
import com.iwhalecloud.citybrain.flow.platform.manager.infrastructure.mapper.FlowWorkingDayDAO;
import com.iwhalecloud.citybrain.flow.platform.manager.infrastructure.mapper.FlowWorkingDayWeekDAO;
import com.iwhalecloud.citybrain.flow.platform.manager.infrastructure.mapper.WorkingDayDAO;
import com.iwhalecloud.citybrain.flow.platform.manager.application.utils.DateUtils;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * @author wuliepeng
 * @date 2019-07-18
 */
@Service
public class WorkingDayRepositoryImpl implements WorkingDayRepository {

    @Autowired
    private FlowWorkingDayDAO flowWorkingDayDAO;

    @Autowired
    private WorkingDayDAO workingDayDAO;

    @Autowired
    private FlowWorkingDayWeekDAO flowWorkingDayWeekDAO;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void addWorkingDay(WorkingDayAndOffDayAddVo workingDayAndOffDayAddVo) {
        List<String> workingDays = workingDayAndOffDayAddVo.getWorkingDays();
        List<OffDayAddVo> offDays = workingDayAndOffDayAddVo.getOffDays();
        Date currentDate = new Date();
        if (CollectionUtils.isNotEmpty(workingDays)) {
            flowWorkingDayWeekDAO.deleteByExample(new FlowWorkingDayWeekExample());
            List<FlowWorkingDayWeek> flowWorkingDayWeeks = new ArrayList<>();
            for (String workingDay : workingDays) {
                FlowWorkingDayWeek flowWorkingDayWeek = new FlowWorkingDayWeek();
                flowWorkingDayWeek.setCode(workingDay);
                flowWorkingDayWeek.setName(WeekEnum.getDescrByCode(workingDay));
                flowWorkingDayWeek.setCreateTime(currentDate);
                flowWorkingDayWeek.setUpdateTime(currentDate);
                flowWorkingDayWeeks.add(flowWorkingDayWeek);
            }
            workingDayDAO.insertWorkingDayWeekBatch(flowWorkingDayWeeks);
        }
        if (CollectionUtils.isNotEmpty(offDays)) {
            List<String> deleteMonths = offDays.stream()
                    .map(item -> DateUtils.dateToString(item.getStart()))
                    .collect(Collectors.toList());
            workingDayDAO.deleteWorkingDay(deleteMonths);
//            workingDayDAO.deleteWorkingDay(DateUtils.dateToString(offDays.get(0).getStart()));
            List<FlowWorkingDay> flowWorkingDays = new ArrayList<>();
            for (OffDayAddVo offDay : offDays) {
                FlowWorkingDay flowWorkingDay = new FlowWorkingDay();
                BeanUtils.copyProperties(offDay, flowWorkingDay);
                flowWorkingDay.setUpdateTime(currentDate);
                flowWorkingDay.setCreateTime(currentDate);
                flowWorkingDays.add(flowWorkingDay);
            }
            workingDayDAO.insertWorkingDayBatch(flowWorkingDays);
        }
    }

    @Override
    public List<WorkingDayInfoVo> listWorkingDay() {
        FlowWorkingDayWeekExample flowWorkingDayWeekExample = new FlowWorkingDayWeekExample();
        List<FlowWorkingDayWeek> flowWorkingDayWeeks = flowWorkingDayWeekDAO.selectByExample(flowWorkingDayWeekExample);
        List<WorkingDayInfoVo> workingDayInfoVos = new ArrayList<>(flowWorkingDayWeeks.size());
        for (FlowWorkingDayWeek flowWorkingDayWeek : flowWorkingDayWeeks) {
            WorkingDayInfoVo workingDayInfoVo = new WorkingDayInfoVo();
            BeanUtils.copyProperties(flowWorkingDayWeek, workingDayInfoVo);
            workingDayInfoVos.add(workingDayInfoVo);
        }
        return workingDayInfoVos;
    }

    @Override
    public List<OffDayInfoVo> listOffDayAndException(WorkingDayQueryVo workingDayQueryVo) {
        List<FlowWorkingDay> flowWorkingDays = workingDayDAO.listOffDayAndException(workingDayQueryVo);
        List<OffDayInfoVo> offDayInfoVos = new ArrayList<>(flowWorkingDays.size());
        for (FlowWorkingDay flowWorkingDay : flowWorkingDays) {
            OffDayInfoVo offDayInfoVo = new OffDayInfoVo();
            BeanUtils.copyProperties(flowWorkingDay, offDayInfoVo);
            offDayInfoVo.setTypeName(WorkingDayTypeEnum.getDescrByCode(offDayInfoVo.getType()));
            offDayInfoVos.add(offDayInfoVo);
        }
        return offDayInfoVos;
    }

    @Override
    public Boolean isTimeout(Date start, Date end, Long duration) {
        List<String> workingDays = getWorkingDays();
        Map<String, List<String>> offDayAndPlusDay = getOffDayAndPlusDay(start, end);
        List<String> offDays = offDayAndPlusDay.get(WorkingDayTypeEnum.OFF_DAY.getCode());
        List<String> plusDays = offDayAndPlusDay.get(WorkingDayTypeEnum.WORKING_DAY.getCode());
        return isTimeout(start, end, workingDays, plusDays, offDays, duration);
    }

    @Override
    public Boolean isTimeout(Date start, Date end, List<String> workingDays, List<String> plusDays, List<String> offDays, Long duration) {
        if (duration == null) {
            return false;
        }
        long actualDuration = (end.getTime() - start.getTime()) / 1000;

        if (!isWorkingDay(workingDays, plusDays, offDays, start)) {
            long startDateDuration = getDurationFromStartDate(start);
            actualDuration -= startDateDuration;
        }
        if (!isWorkingDay(workingDays, plusDays, offDays, end)) {
            long endDateDuration = getDurationToEndDate(end);
            actualDuration -= endDateDuration;
        }
        int notWorkingDayNum = getNotWorkingDayNum(workingDays, plusDays, offDays, start, end);
        actualDuration -= notWorkingDayNum * 24 * 3600;
        if (actualDuration < duration) {
            return false;
        }
        return true;
    }

    @Override
    public List<String> getWorkingDays() {
        // 获取工作日列表(以周为维度,每周的工作日)
        FlowWorkingDayWeekExample flowWorkingDayWeekExample = new FlowWorkingDayWeekExample();
        List<FlowWorkingDayWeek> flowWorkingDayWeeks = flowWorkingDayWeekDAO.selectByExample(flowWorkingDayWeekExample);
        List<String> workingDays = flowWorkingDayWeeks.stream().map(item -> item.getCode()).collect(Collectors.toList());
        return workingDays;
    }

    @Override
    public Map<String, List<String>> getOffDayAndPlusDay(Date start, Date end) {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");

        // 获取节假日和工作日设置信息
        FlowWorkingDayExample plusDayExample = new FlowWorkingDayExample();
        plusDayExample.createCriteria().andStartBetween(start, end);
        List<FlowWorkingDay> flowWorkingDays = flowWorkingDayDAO.selectByExample(plusDayExample);

        // 分组,分别是节假日和工作日
        Map<String, List<FlowWorkingDay>> offDayAndPlusDay = flowWorkingDays.stream().collect(Collectors.groupingBy(FlowWorkingDay::getType));

        // 分别获取节假日和工作日设置记录中的日期列表
        List<FlowWorkingDay> offDays = offDayAndPlusDay.get(WorkingDayTypeEnum.OFF_DAY.getCode());
        List<FlowWorkingDay> plusDays = offDayAndPlusDay.get(WorkingDayTypeEnum.WORKING_DAY.getCode());
        Map offDayAndPlusDayDateList = new HashMap(2);
        if (CollectionUtils.isNotEmpty(offDays)) {
            List<String> offDaysDateList = offDays.stream().map(item -> sdf.format(item.getStart())).collect(Collectors.toList());
            offDayAndPlusDayDateList.put(WorkingDayTypeEnum.OFF_DAY.getCode(), offDaysDateList);
        }
        if (CollectionUtils.isNotEmpty(plusDays)) {
            List<String> plusDaysDateList = plusDays.stream().map(item -> sdf.format(item.getStart())).collect(Collectors.toList());
            offDayAndPlusDayDateList.put(WorkingDayTypeEnum.WORKING_DAY.getCode(), plusDaysDateList);
        }
        return offDayAndPlusDayDateList;
    }

    private int getNotWorkingDayNum(List<String> workingDay, List<String> plusDay, List<String> offDay, Date startDate, Date endDate) {
        int notWorkingDayNum = 0;
        Calendar startDateCalendar = Calendar.getInstance();
        Calendar endDateCalendar = Calendar.getInstance();
        startDateCalendar.setTime(startDate);
        endDateCalendar.setTime(endDate);
        startDateCalendar.add(Calendar.DATE, 1);
        endDateCalendar.add(Calendar.DATE, -1);
        while (!startDateCalendar.after(endDateCalendar)) {
            if (!isWorkingDay(workingDay, plusDay, offDay, startDateCalendar.getTime())) {
                notWorkingDayNum++;
            }
            startDateCalendar.add(Calendar.DATE, 1);
        }
        return notWorkingDayNum;
    }

    private boolean isWorkingDay(List<String> workingDay, List<String> plusDay, List<String> offDay, Date sourceDate) {
        if (CollectionUtils.isEmpty(workingDay)) {
            workingDay = new ArrayList<>(1);
        }
        if (CollectionUtils.isEmpty(plusDay)) {
            plusDay = new ArrayList<>(1);
        }
        if (CollectionUtils.isEmpty(offDay)) {
            offDay = new ArrayList<>(1);
        }
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(sourceDate);
        String weekIndex = String.valueOf(calendar.get(Calendar.DAY_OF_WEEK));
        String sourceDateStr = DateUtils.dateToString(calendar.getTime(), "yyyy-MM-dd");
        boolean isWorkingDay = (workingDay.contains(weekIndex) || plusDay.contains(sourceDateStr)) && !offDay.contains(sourceDateStr);
        if (isWorkingDay) {
            return true;
        }
        return false;
    }

    private long getDurationFromStartDate(Date startDate) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(startDate);
        calendar.set(Calendar.HOUR_OF_DAY, 23);
        calendar.set(Calendar.MINUTE, 0);
        calendar.set(Calendar.SECOND, 0);
        Date endDate = calendar.getTime();
        return (endDate.getTime() - startDate.getTime()) / 1000;
    }

    private long getDurationToEndDate(Date endDate) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(endDate);
        calendar.set(Calendar.HOUR_OF_DAY, 0);
        calendar.set(Calendar.MINUTE, 0);
        calendar.set(Calendar.SECOND, 0);
        Date startDate = calendar.getTime();
        return (endDate.getTime() - startDate.getTime()) / 1000;
    }

}
